<?php

class CRequest extends CBase {
	
	function __construct() {
		parent::__construct();
	}
	
	private $_routeDefault = array();
	
	/**
	 * 执行uri请求:根据uri及路由规则定位到控制器并执行动作(action)
	 */
	public function exec()
	{
		$pathArr = $this->parseUri();
		$route = $this->pathMath($pathArr);

		$dir = $route["dir"];
		$controller = $route["controller"];
		$action = $route["action"];
		
		$actionDir = Config::get("controllerDir");
		
		$actionDir = $this->getAbsolutePath($actionDir);

		$actionDir = $actionDir.$dir;
		
		if(!is_dir($actionDir))
		{
			CError::handler(E_ERROR,"路径".$actionDir."不存在",__FILE__,__LINE__);
		}
		
		$filePath = "";
		
		if(substr($actionDir,strlen($actionDir)-1,1)!="/")
		{
			$filePath = $actionDir."/".$controller.".php";
		}
		else
		{
			$filePath = $actionDir.$controller.".php";
		}

		if(!is_file($filePath))
		{
			CError::handler(E_ERROR,"文件".$filePath."不存在",__FILE__,__LINE__);
		}
		
		require_once $filePath;
		
		$clsParents = class_parents($controller);
		
		if(!isset($clsParents["CController"]))
		{
			CError::handler(E_ERROR,"控制器[".$controller."]必须继承自CController",__FILE__,__LINE__);
		}
		
		$controllerObj = new $controller($controller, $action);
		
		if(!method_exists($controllerObj,$action))
		{
			CError::handler(E_ERROR,"动作[".$action."]不存在",__FILE__,__LINE__);
		}
		
		$controllerObj->$action();
	}
	
	/**
	 * 分析uri,将路径(不包括域名和参数)拆分成数组返回,返回值将提供给路由匹配
	 * @return array 如:http://www.a.com/a/b/c,返回array("a","b","c"),http://www.a.com/a/b/?a=1,返回array("a","b")
	 */
	public function parseUri()
	{
		$uriPath = "";
		
		if(isset($_SERVER["PATH_INFO"]))
		{
			$uriPath=$_SERVER["PATH_INFO"];
		}
		else{
			$uriPath = $_SERVER["REQUEST_URI"];
			
			if(strpos($uriPath,"?")!==false)
			{
				$uriList = explode('?',$uriPath);
				$uriPath = $uriList[0];
			}
		}
		
		$pathArr = array();
		$temp = explode("/",$uriPath);
		
		foreach ($temp as $k=>$item)
		{
			if ($item)
			{
				$pathArr [] = $item;
			}
		}
		
		return $pathArr;
	}
	
	/**
	 * Uri匹配路由规则
	 * @param array $pathArr parseUri方法返回的参数
	 * @return 返回匹配成功后的目录/控制器/动作  array("dir"=>dir,"controller"=>controller,"action"=>action)
	 */
	public function pathMath($pathArr)
	{
		$routeUri = Config::get("route.uri");
		$routeDefault = Config::get("route.default");
		
		$this->_routeDefault = $routeDefault;

		$pathLen = count($pathArr); //当前路径深度
		
		//$routeItem = array(); //保存匹配的路由规则
		$routeItemStr = "";   //保存匹配的路由规则对应的重新规则
		$firstRouteItem = array(); //保存第一个路由规则
		
		$routeTemp = array(); //临时变量
		
		$minRouteLen = 0; //存放最短的路由长度
		$maxRouteLen = 0; //存放最长的路由长度
		
		
		//保存uri里的路径，控制器，action
		$dir = "";
		$controller = "";
		$action = "";
		$match = array(); //数组方式保存dir,controller,action,作为返回值
		
		//直接域名访问,从配置文件取默认值
		if($pathLen==0)
		{
			$dir = isset($routeDefault["dir"])?$routeDefault["dir"]:""; 
			$controller = isset($routeDefault["controller"])?$routeDefault["controller"]:""; 
			$action = isset($routeDefault["action"])?$routeDefault["action"]:"";
			
			if(!$controller || !$action)
			{
				CError::handler(E_ERROR,"请设置默认路由",__FILE__,__LINE__);
			}
			
			$match = array("dir"=>$dir,"controller"=>$controller, "action"=>$action);
		}
		else
		{
			if(!is_array($routeUri))
			{
				$routeUri = array($routeUri);
			}
			
			foreach($routeUri as $k=>$item)
			{
				$routeList = array();
				$routeItemStr = "";
				if(is_array($item))
				{
					$routeTemp = array_keys($item);
					$itemKey = $routeTemp[0];
					$routeItemStr = $item[$itemKey];
					$item = $itemKey;
				}
				$routeTemp = explode("/",$item);
				foreach ($routeTemp as $item1) {
					if($item1)
					{
						$routeList[] = $item1;
					}
				}

				//记录第一个路由
				if($k==0)
				{
					$firstRouteItem = $routeList;
				}
				
				//判断uri和路由是否匹配
				if(count($routeList) === $pathLen)
				{
					//$routeItem = $routeList;
					$match = $this->routeMatch($routeList,$pathArr);
					if($match) //规则已经匹配到
					{
						break;
					}
				}
				
				//得到最短路由长度
				if($minRouteLen==0 || count($routeList)<$minRouteLen)
				{
					$minRouteLen = count($routeList);
				}
				
				//得到最长路由长度
				if(count($routeList)>$maxRouteLen)
				{
					$maxRouteLen = count($routeList);
				}
			}
			
			//没找到匹配的路由并且uri深度小于所有路由里面深度最小的，则匹配第一个路由
			if(!$match && $pathLen < $minRouteLen)
			{
				$match = $this->routeMatch($firstRouteItem,$pathArr,false);
			}

			if(!$match) //不存在匹配的路由规则
			{
				/*$dir = isset($routeDefault["dir"])?$routeDefault["dir"]:""; 

				$controller = isset($routeDefault["controller"])?$routeDefault["controller"]:""; 

				$action = isset($routeDefault["action"])?$routeDefault["action"]:"";

				if(!$controller || !$action)
				{*/
					CError::handler(E_ERROR,"无法匹配路由",__FILE__,__LINE__);
				/*}
				
				$match = array("dir"=>$dir,"controller"=>$controller, "action"=>$action);*/
			}
		}
		//var_dump($match);
		return $match;//array("dir"=>$dir,"controller"=>$controller, "action"=>$action);
	}
	
	/**
	 * 验证一条指定的路由规则是否匹配uri路径
	 * @param array $routeItem 指定的路由规则
	 * @param array $pathArr uri路径(数组形式,由parseUri方法返回)
	 * @param bool $flag 标记路径规则和uri深度是否一致，true表示一致
	 */
	private function routeMatch($routeItem,$pathArr,$flag=true)
	{
		//保存uri里的路径，控制器，action
		$dir = "";
		$controller = "";
		$action = "";
		
		$item = ""; //路由里的子项
		$urlItem = ""; //uri路径里的子项
		
		$itemOther = ""; //存放路由规则里面除了<>()等正常规则之外多出来的前面部分,比如aa<action>,这里的aa就算是多出来的
		$itemOtherB = ""; //存放路由规则里面除了<>()等正常规则之外多出来的后面部分,比如<action>.html,这里的.html就算是多出来的
		
		$itemTemp = ""; //临时变量
		
		$isMatch = true; //记录路由和uri使用需要用正则匹配时,是否匹配成功
		
		$pathLen = count($pathArr); //当前路径深度
		
		for($i=0;$i<$pathLen;$i++)
		{
			$item = $routeItem[$i];
			$urlItem = $pathArr[$i];
			$itemOther = "";
			$itemOtherB = "";
			
			$itemPos = false;
			
			if(($itemPos=strpos($item,"<dir>"))!==false)
			{
				if($itemPos>0)
				{
					$itemOther = substr($item,0,$itemPos);
				}
				
				$itemOtherB = substr($item,$itemPos+5);
				
				if($itemOtherB===false)
				{
					$itemOtherB = "";
				}
				
				if(substr($urlItem,0,$itemPos)===$itemOther && substr($urlItem,-(strlen($itemOtherB)),strlen($itemOtherB))===$itemOtherB)
				{
					$itemTemp = substr($urlItem,$itemPos,strlen($urlItem)-strlen($itemOther)-strlen($itemOtherB));
					
					if($itemOther."<dir>".$itemOtherB === $item)
					{
						if($dir)
						{
							$dir .= "/".$itemTemp;
						}
						else
						{
							$dir = $itemTemp;
						}
					}
				}
				else
				{
					$isMatch = false;
				}
			}
			else if(($itemPos=strpos($item,"<controller>"))!==false)
			{
				if($itemPos>0)
				{
					$itemOther = substr($item,0,$itemPos);
				}
				
				$itemOtherB = substr($item,$itemPos+strlen("<controller>"));
				
				if($itemOtherB===false)
				{
					$itemOtherB = "";
				}
				//var_dump($itemOtherB);
				if(substr($urlItem,0,$itemPos)===$itemOther && substr($urlItem,-(strlen($itemOtherB)),strlen($itemOtherB))===$itemOtherB)
				{
					$itemTemp = substr($urlItem,$itemPos,strlen($urlItem)-strlen($itemOther)-strlen($itemOtherB));
					
					if($itemOther."<controller>".$itemOtherB === $item)
					{
						$controller = $itemTemp;
					}
				}
				else
				{
					$isMatch = false;
				}
				
				/*$itemOther = str_replace("<controller>","",$item);
				$controller = str_replace($itemOther,"",$urlItem);
				
				if(str_replace($controller,"<controller>",$urlItem) != $item)
				{
					$isMatch = false;
				}*/
			}
			else if(($itemPos=strpos($item,"<action>"))!==false)
			{
				if($itemPos>0)
				{
					$itemOther = substr($item,0,$itemPos);
				}
				
				$itemOtherB = substr($item,$itemPos+strlen("<action>"));
				
				if($itemOtherB===false)
				{
					$itemOtherB = "";
				}

				if(substr($urlItem,0,$itemPos)===$itemOther && substr($urlItem,-(strlen($itemOtherB)),strlen($itemOtherB))===$itemOtherB)
				{
					$itemTemp = substr($urlItem,$itemPos,strlen($urlItem)-strlen($itemOther)-strlen($itemOtherB));
					
					if($itemOther."<action>".$itemOtherB === $item)
					{
						$action = $itemTemp;
					}
				}
				else
				{
					$isMatch = false;
				}
				
				/*$itemOther = str_replace("<action>","",$item);
				$action = str_replace($itemOther,"",$urlItem);
				if(str_replace($action,"<action>",$urlItem) != $item)
				{
					$isMatch = false;
				}*/
			}
			else
			{
				$getParam = "";
				$getParamValue = "";
				$regStr = "";
				$isMatch = true;
				$matches = array();

				preg_match("/^(?<otherA>[\\s\\S]+)?\\<(?<param>[^>]+)\\>(\\((?<regexStr>[^)]+)\\))?(?<otherB>[\\s\\S]+)?$/",$item,$matches);
				
				if(!$matches)
				{
					$isMatch = false;
					break;
				}
				
				//var_dump($matches);
				
				$getParam = $matches["param"];
				$regStr = isset($matches["regexStr"])?$matches["regexStr"]:"";
				$itemOther = isset($matches["otherA"])?$matches["otherA"]:"";
				$itemOtherB = isset($matches["otherB"])?$matches["otherB"]:"";
				
				$urlItemOld = $urlItem;
					
				$urlItem = str_replace($itemOther,"",$urlItem);
				$urlItem = str_replace($itemOtherB,"",$urlItem);
				
				if($regStr) //如果路由里有正则表达式，则用正则验证uri对应项
				{
					$regStrOld = $regStr;
					$regStr = "/".$regStr."/";
					
					preg_match($regStr,$urlItem,$matches);
					
					if(count($matches)>=1) //验证通过
					{
						//由于$urlItem用了str_replace函数，并不需要考虑替换字符串所在的位置，所以这里需要重新验证下是否完全符合路由规则
						if(str_replace($urlItem,"<".$getParam.">"."(".$regStrOld.")",$urlItemOld) != $item)
						{
							$isMatch = false;
						}
						else
						{
							$getParamValue = $urlItem;
						}
					}
					else
					{
						$isMatch = false;
						break;
					}
				}
				else {
					if(str_replace($urlItem,"<".$getParam.">",$urlItemOld) != $item)
					{
						$isMatch = false;
					}
					else {
						$getParamValue = $urlItem;
					}
				}

				if($getParam)
				{
					$_GET[$getParam] = $getParamValue;
				}
			}
		}
		
		if(!$isMatch) //正则匹配失败,则该路由匹配失败
		{
			return false;
		}
		
		//$flag标记为false,表示uri和现有路由匹配不到,但是由于uri深度小于所有路由规则深度,比如首页
		//可以默认匹配第一个路由,这时候控制器和动作可以到默认值里去取
		if(!$flag)
		{
			if(!$controller)
			{
				$controller = isset($this->_routeDefault["controller"])?$this->_routeDefault["controller"]:""; 
			}
			
			if(!$action)
			{
				$action = isset($this->_routeDefault["action"])?$this->_routeDefault["action"]:"";
			}
		}
		
		if($controller && $action)
		{
			return array("dir"=>$dir,"controller"=>$controller, "action"=>$action);
		}

		return false;
	}
}

?>